<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\ConfModel;

use Google\Protobuf\Any;
use Srv\Apps\Proto\Enum\ErrCodeEnum;
use Srv\Apps\Proto\Enum\LangKeyEnum;
use Srv\Apps\Proto\VO\LimitSpeedErrorVO;
use Srv\Apps\Proto\VO\ParamCheckErrorVO;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommValid;
use Srv\Libs\Frame\ActionAbstract;
use Srv\Libs\Frame\CheckDTO;
use Srv\Libs\Frame\CheckLimit;
use Srv\Libs\Frame\DTOAbstract;
use Srv\Libs\Frame\Lang;
use Srv\Libs\Frame\ResponseData;

final class RouteConfData
{
    private string $className                   = '';       // Class
    private string $methodName                  = '';       // Method
    private string $authStr                     = '';       // Auth
    private string $codeStr                     = '';       // Code
    private string $DTOClassName                = '';       // DTOClassName
    private string $VOClassName                 = '';       // VOClassName
    private array $DTOCheckConf                 = [];       // DTOCheckConf
    private array $FlagList                     = [];       // FlagList
    private array $ErrVOClassNameList           = [];       // ErrVOClassNameList
    private array $LimitList                    = [];       // LimitList
    private string $groupName                   = '';       // 组名
    private string $urlPath                     = '';       // 原UrlPath
    private string $urlPathSet                  = '';       // 原UrlPathSet
    private bool $webSocket                     = false;    // 是否是WebSocket
    private array $urlPathVarList               = [];       // urlPathVarList
    private string $reqContentSrc               = '';       // reqContentSrc
    private string $reqContent                  = '';       // reqContent
    private array $paramsSrcData                = [];       // 参数JSON或XML转出的原数组信息

    /**
     * @param array $urlConfData
     * @param string $groupName
     * @param string $urlPath
     * @param bool $webSocket
     * @param array $urlPathVarList
     * __construct
     */
    public function __construct(array $urlConfData, string $groupName, string $urlPath, bool $webSocket, array $urlPathVarList)
    {
        if(isset($urlConfData[0]) && is_string($urlConfData[0])) $this->className           = $urlConfData[0];
        if(isset($urlConfData[1]) && is_string($urlConfData[1])) $this->methodName          = $urlConfData[1];
        if(isset($urlConfData[2]) && is_string($urlConfData[2])) $this->authStr             = $urlConfData[2];
        if(isset($urlConfData[3]) && is_string($urlConfData[3])) $this->codeStr             = $urlConfData[3];
        if(isset($urlConfData[4]) && is_string($urlConfData[4])) $this->DTOClassName        = $urlConfData[4];
        if(isset($urlConfData[5]) && is_string($urlConfData[5])) $this->VOClassName         = $urlConfData[5];
        if(isset($urlConfData[6]) && is_array($urlConfData[6]))  $this->DTOCheckConf        = $urlConfData[6];
        if(isset($urlConfData[7]) && is_array($urlConfData[7]))  $this->FlagList            = $urlConfData[7];
        if(isset($urlConfData[8]) && is_array($urlConfData[8]))  $this->ErrVOClassNameList  = $urlConfData[8];
        if(isset($urlConfData[9]) && is_array($urlConfData[9]))  $this->LimitList           = $urlConfData[9];
        $this->groupName        = $groupName;
        $this->urlPath          = $urlPath;
        $this->webSocket        = $webSocket;
        $this->urlPathVarList   = $urlPathVarList;
        $this->paramsSrcData    = [];
        ActionAbstract::setRouteGroupName($groupName);
    }

    /**
     * @param $params // array,string,DTO
     * @param bool $isCheckType
     * @return bool
     * runAction
     */
    public function runAction($params, bool $isCheckType = true):bool
    {
        // LIMIT
        if(count($this->LimitList) > 0){
            CheckLimit::init($this->urlPath, $this->className.'::'.$this->methodName);
            foreach ($this->LimitList as $limitItem){
                $errCode                = ErrCodeEnum::ERR_SYS_LIMIT_ERROR;
                $errMsg                 = '';
                $isBreak                = $limitItem[2]??false;
                if(!CheckLimit::checkSpeed($limitItem[0], $limitItem[1], $limitItem[3], $limitItem[4], $limitItem[5], $isBreak)){
                    ActionAbstract::addRunLog('checkSpeedFail: '.$this->urlPath.', '.($this->className.'->'.$this->methodName).', '.CommJson::encodeArray($limitItem));
                    return ResponseData::rspErrAuto($errCode, $errMsg, new LimitSpeedErrorVO(['speedType' => $limitItem[0], 'speedConf' => $limitItem[6]??'']));
                }
                if($isBreak) break;
            }
        }
        // 参数
        $DTOClassObject             = null;
        if(strlen($this->DTOClassName) > 0){
            $optionalAll            = $this->DTOCheckConf[2];
            if(!$optionalAll && is_null($params)){               // 参数空
                ActionAbstract::addRunLog('checkParamFail: Empty FAIL');
                return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EMPTY, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EMPTY));
            }
            $DTOClassObject         = new $this->DTOClassName();
            if(!($DTOClassObject instanceof DTOAbstract)){
                ActionAbstract::addRunLog('checkParamFail: Not Instanceof DTOAbstract FAIL');
                return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EXCEPTION));
            }
            if(is_array($params)){  // 来自JSON或XML
                $paramsSize         = count($params);
                if(!$optionalAll && $paramsSize < 1){
                    ActionAbstract::addRunLog('checkParamFail: Array Empty FAIL');
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EMPTY, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EMPTY));
                }
                $errKeyName         = '';
                $errKeyType         = '';
                $errKeyValue        = null;
                if($paramsSize > 0 && !$DTOClassObject->mergeFromArrayTry($params, true, false, $errKeyName, $errKeyType, $errKeyValue)){
                    ActionAbstract::addRunLog('checkParamFail: mergeFromArrayTry FAIL, '.$errKeyName.', '.$errKeyType.', '.CommString::getVarString($errKeyValue));
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EXCEPTION));
                }
                $checkParam             = $params;
                $this->paramsSrcData    = $params;
            }else if($params instanceof Any){   // protobuf转换DTO
                $AnyOAbstract       = $DTOClassObject->anyTypeUrlToMessage($params);
                if(!($AnyOAbstract instanceof $DTOClassObject)){
                    ActionAbstract::addRunLog('checkParamFail: Not Equal DTOAbstract FAIL');
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_ERROR, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_ERROR));
                }
                $DTOClassObject     = $AnyOAbstract;
                $checkParam         = $DTOClassObject->getDataJsonArray([], true);
            }else if($params instanceof DTOAbstract){
                if(!($params instanceof $DTOClassObject)){
                    ActionAbstract::addRunLog('checkParamFail: Not Equal DTOAbstract FAIL');
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_ERROR, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_ERROR));
                }
                $DTOClassObject     = $params;
                $checkParam         = $DTOClassObject->getDataJsonArray([], true);
            }else if(is_string($params)){
                $paramsLength       = strlen($params);
                if(!$optionalAll && $paramsLength < 1){
                    ActionAbstract::addRunLog('checkParamFail: String Empty FAIL');
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EMPTY, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EMPTY));
                }
                if($paramsLength > 0 && !$DTOClassObject->mergeFromStrTry($params)){
                    ActionAbstract::addRunLog('checkParamFail: mergeFromStrTry FAIL');
                    return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EXCEPTION));
                }
                $checkParam         = $DTOClassObject->getDataJsonArray([], true);
            }else{
                $checkParam         = [];
            }
            if(!$optionalAll && count($checkParam) < 1){         // 参数空
                ActionAbstract::addRunLog('checkParamFail: DTOAbstract Empty FAIL');
                return ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_PARAMS_EMPTY, Lang::get(LangKeyEnum::LANG_SYS_PARAMS_EMPTY));
            }
            if(count($this->DTOCheckConf) > 1){ // 自动校验参数
                CheckDTO::init($this->DTOCheckConf[0]);
                $errCode            = ErrCodeEnum::ERR_SYS_PARAMS_CHECK_AUTO_FAIL;
                foreach ($this->DTOCheckConf[1] as $checkConfData){
                    $propertyName   = $checkConfData['name'];
                    $checkConf      = $checkConfData['check'];
                    $comment        = $checkConfData['comment'];
                    $errMsgSet      = $checkConfData['errMsgSet'];
                    $checkType      = $checkConf[0]??'';
                    if(strlen($errMsgSet) > 0){
                        $errMsg     = 'Error，'.$errMsgSet;
                    }else{
                        if(strlen($comment) > 0){
                            $errMsg = 'Error，'.$propertyName;
                        }else{
                            $errMsg = Lang::get(LangKeyEnum::LANG_SYS_PARAMS_CHECK_AUTO_FAIL, ['PARAM_NAME' => $propertyName, 'CHECK_TYPE' => $checkType]);
                        }
                    }
                    switch ($checkType){
                        case CheckDTO::CHECK_TYPE_NUMBER:{
                            CheckDTO::setNumber($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_NUMBER_STR_LEN:{
                            CheckDTO::setNumberStrLen($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_NUMBER_STR_INT:{
                            CheckDTO::setNumberStrInt($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_STRING:{
                            CheckDTO::setString($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_FLOAT:{
                            CheckDTO::setFloat($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_FLOAT_STR:{
                            CheckDTO::setFloatStr($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ENUM:{
                            CheckDTO::setEnum($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_BOOL:{
                            CheckDTO::setBool($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY:{
                            CheckDTO::setArray($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_NUM:{
                            CheckDTO::setArrayNumber($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_STR:{
                            CheckDTO::setArrayStr($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_FLOAT:{
                            CheckDTO::setArrayFloat($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4], $checkConf[5], $checkConf[6]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_FLOAT_STR:{
                            CheckDTO::setArrayFloatStr($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4], $checkConf[5], $checkConf[6]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_ENUM:{
                            CheckDTO::setArrayEnum($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_BOOL:{
                            CheckDTO::setArrayBool($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ENUM:{
                            CheckDTO::setClassEnum($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ARRAY_ENUM:{
                            CheckDTO::setClassArrayEnum($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_MESSAGE:{
                            CheckDTO::setClassMessage($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2]);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ARRAY_MESSAGE:{
                            CheckDTO::setClassArrayMessage($propertyName, $errCode, $errMsg, $checkConf[1], $checkConf[2], $checkConf[3], $checkConf[4]);
                            break;
                        }
                        default:{
                            CheckDTO::setValid($propertyName, $errCode, $errMsg, $checkConf[0], $checkConf[1]??[]);
                        }
                    }
                }
                $paramName          = '';
                $paramValueJson     = '';
                $errCode            = 0;
                $errMsg             = '';
                $checkType          = '';
                if(!CheckDTO::checkParam($checkParam, $paramName, $paramValueJson, $errCode, $errMsg, $checkType, $isCheckType)){
                    ActionAbstract::addRunLog('checkParamFail: '.$this->urlPath.', '.($this->className.'->'.$this->methodName).', '.$checkType.', '.$paramName.', '.$paramValueJson);
                    return ResponseData::rspErrAuto($errCode, $errMsg, new ParamCheckErrorVO(['paramName' => $paramName, 'paramValue' => $paramValueJson, 'checkType' => $checkType]));
                }
            }
        }
        $classObject                = new $this->className();
        $VOClassObject              = strlen($this->VOClassName) < 1 ? null : new $this->VOClassName();
        $classObject->init();
        return !(call_user_func_array([$classObject, $this->methodName], [$this, $DTOClassObject, $VOClassObject]) === false);
    }

    /**
     * @param string $reqContent
     * @param string $reqContentSrc
     * @param string $urlPathSet
     * @return void
     * initReqContent
     */
    public function initReqContent(string $reqContent, string $reqContentSrc, string $urlPathSet = ''):void
    {
        $this->reqContentSrc        = $reqContentSrc;
        $this->reqContent           = $reqContent;
        $this->urlPathSet           = $urlPathSet;
        ResponseData::setRouteConfData($this);
    }

    /**
     * @param string $paramName
     * @return bool
     * isSetParam
     */
    public function isSetParam(string $paramName):bool
    {
        if(strlen($paramName) < 1) return false;
        return array_key_exists($paramName, $this->paramsSrcData);
    }

    /**
     * @return string
     */
    public function getClassName(): string
    {
        return $this->className;
    }

    /**
     * @return string
     */
    public function getMethodName(): string
    {
        return $this->methodName;
    }

    /**
     * @return string
     */
    public function getAuthStr(): string
    {
        return $this->authStr;
    }

    /**
     * @return string
     */
    public function getCodeStr(): string
    {
        return $this->codeStr;
    }

    /**
     * @return string
     */
    public function getDTOClassName(): string
    {
        return $this->DTOClassName;
    }

    /**
     * @return string
     */
    public function getVOClassName(): string
    {
        return $this->VOClassName;
    }

    /**
     * @return array
     */
    public function getDTOCheckConf(): array
    {
        return $this->DTOCheckConf;
    }

    /**
     * @return array
     */
    public function getFlagList(): array
    {
        return $this->FlagList;
    }

    /**
     * @return array
     */
    public function getErrVOClassNameList(): array
    {
        return $this->ErrVOClassNameList;
    }

    /**
     * @return string
     */
    public function getGroupName(): string
    {
        return $this->groupName;
    }

    /**
     * @return string
     */
    public function getUrlPath(): string
    {
        return $this->urlPath;
    }

    /**
     * @return string
     */
    public function getUrlPathSet(): string
    {
        return $this->urlPathSet;
    }

    /**
     * @return bool
     */
    public function isWebSocket(): bool
    {
        return $this->webSocket;
    }

    /**
     * @return string
     */
    public function getReqContentSrc(): string
    {
        return $this->reqContentSrc;
    }

    /**
     * @return string
     */
    public function getReqContent(): string
    {
        return $this->reqContent;
    }

    /**
     * @return array
     */
    public function getParamsSrcData(): array
    {
        return $this->paramsSrcData;
    }

    /**
     * @param DTOAbstract|null $DTOClass
     * @return array
     * getParamsResData
     */
    public function getParamsResData(?DTOAbstract $DTOClass):array
    {
        if(!($DTOClass instanceof DTOAbstract)) return [];
        return $this->getArrayData($DTOClass->getOMessageJsonArrayForDefault(), $this->paramsSrcData);
    }

    /**
     * @param array $defAry
     * @param array $valAry
     * @return array
     * getArrayData
     */
    private function getArrayData(array $defAry, array $valAry):array
    {
        $resAry             = [];
        if(count($defAry) < 1 || count($valAry) < 1) return $resAry;
        foreach ($defAry as $defKey => $defVal){
            if(!isset($valAry[$defKey])) continue;
            if(is_array($defVal) && CommValid::isArrayObject($defVal)){
                if(is_array($valAry[$defKey]) && CommValid::isArrayObject($valAry[$defKey])) $resAry[$defKey] = $this->getArrayData($defVal, $valAry[$defKey]);
            }else{
                $resAry[$defKey] = $valAry[$defKey];
            }
        }
        return $resAry;
    }

    /**
     * @return array
     * getUrlPathVarList
     */
    public function getUrlPathVarList():array
    {
        return $this->urlPathVarList;
    }

    /**
     * @param string $varName
     * @param bool $isSet
     * @param bool $isAll
     * @return array|mixed|string
     * getUrlPathVar
     */
    private function getUrlPathVar(string $varName, bool &$isSet = false, bool $isAll = false)
    {
        $varList    = [];
        $isSet      = false;
        foreach ($this->urlPathVarList as $urlPathVal){
            if($urlPathVal[0] !== $varName) continue;
            if(!$isSet) $isSet = !($urlPathVal[2] && $urlPathVal[1] === '');
            if(!$isAll) return $urlPathVal[1];
            $varList[] = $urlPathVal[1];
        }
        return !$isAll ? '' : $varList;
    }

    /**
     * @param string $varName
     * @param bool $isSet
     * @return string
     * getUrlPathVarStr
     */
    public function getUrlPathVarStr(string $varName, bool &$isSet = false):string
    {
        return strval($this->getUrlPathVar($varName, $isSet, false));
    }

    /**
     * @param string $varName
     * @param bool $isSet
     * @return int
     * getUrlPathVarInt
     */
    public function getUrlPathVarInt(string $varName, bool &$isSet = false):int
    {
        return intval($this->getUrlPathVar($varName, $isSet, false));
    }

    /**
     * @param string $varName
     * @param bool $isSet
     * @return float
     * getUrlPathVarFloat
     */
    public function getUrlPathVarFloat(string $varName, bool &$isSet = false):float
    {
        return floatval($this->getUrlPathVar($varName, $isSet, false));
    }

    /**
     * @param string $varName
     * @param bool $isSet
     * @return array
     * getUrlPathVarArray
     */
    public function getUrlPathVarArray(string $varName, bool &$isSet = false):array
    {
        $varList    = $this->getUrlPathVar($varName, $isSet, true);
        return is_array($varList) ? $varList : [];
    }
}